<!DOCTYPE html>
<!--
  The MIT License (MIT)

  Copyright (c) 2017 Tarek Sherif

  Permission is hereby granted, free of charge, to any person obtaining a copy of
  this software and associated documentation files (the "Software"), to deal in
  the Software without restriction, including without limitation the rights to
  use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
  the Software, and to permit persons to whom the Software is furnished to do so,
  subject to the following conditions:

  The above copyright notice and this permission notice shall be included in all
  copies or substantial portions of the Software.

  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
  FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
  COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
  IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
  CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-->
<!-- 
    Based on "Weighted Blended Order-Independent Transparency"
    By Morgan McGuire and Louis Bavoil 
    http://jcgt.org/published/0002/02/09/
-->
<html>
<head>
    <style>
        html {
            overflow: hidden;
        }

        body {
            margin: 0;
        }
    </style>
</head>
<body>
    <canvas id="gl-canvas"></canvas>
    <script type="x-shader/vs" id="vertex-accum">
        #version 300 es

        layout(std140, column_major) uniform;
        
        layout(location=0) in vec4 position;
        
        uniform SceneUniforms {
            mat4 uViewProj;
        };

        uniform ObjectUniforms {
            vec4 color;
            vec3 translation;
        };
        
        out vec3 vPosition;

        void main() {
            gl_Position = uViewProj * (position + vec4(translation, 0.0));
        }
    </script>
    <script type="x-shader/fs" id="fragment-accum">
        #version 300 es
        precision highp float;

        ////////////////////////
        // WEIGHT FUNCTIONS
        ////////////////////////

        // From: https://casual-effects.blogspot.com/2015/03/implemented-weighted-blended-order.html
        #define BLOG 1

        // From Morgan's message on Twitter
        #define TWITTER 2

        // Tweaks to Morgans's weight function
        #define MODIFIED_TWITTER 3

        //From: http://casual-effects.com/research/McGuire2017Transparency/index.html
        #define PERCEP_PAPER 3

        // My own. An 8th degree polynomial on depth.
        #define POLY_D 4

        ////////////////////////
        // SET WEIGHT FUNCTION
        ////////////////////////

        #define WEIGHT_FUNC TWITTER

        layout(std140, column_major) uniform;

        uniform ObjectUniforms {
            vec4 color;
            vec3 translation;
        };


        layout(location=0) out vec4 accumColor;
        layout(location=1) out float accumAlpha;
        
        float weight(float a) {  
            #if WEIGHT_FUNC == BLOG
                return clamp(pow(min(1.0, a * 10.0) + 0.01, 3.0) * 1e8 * pow(1.0 - gl_FragCoord.z * 0.9, 3.0), 1e-2, 3e3);
            #elif WEIGHT_FUNC == TWITTER   
                float tmp = 1.0 - gl_FragCoord.z * 0.99; 
                tmp *= tmp * tmp * 1e4;
                tmp = clamp(tmp, 1e-3, 1.0);
                return clamp(a * tmp, 1e-3, 1.5e2); 
            #elif WEIGHT_FUNC == MODIFIED_TWITTER
                float tmp = 1.0 - gl_FragCoord.z * 0.99;
                tmp *= tmp * tmp * 1e4; 
                return clamp(a * tmp, 1e-3, 3e4);
            #elif WEIGHT_FUNC == PERCEP_PAPER
                float tmp = 10.0 * (1.0 - 0.99 * gl_FragCoord.z) * a;
                float tmp *= tmp * tmp;
                return clamp(tmp, 0.01, 30.0);
            #elif WEIGHT_FUNC == POLY_D
                float d = 1.0 - gl_FragCoord.z * 0.99;
                float d2 = d * d;
                float d4 = d2 * d2;
                float d8 = d4 * d4;
                float d16 = d8 * d8;
                float depthWeight = d + 10.0 * d2 + 1e2 * d4 + 1e5 * d16;
                return clamp(a * depthWeight, 0.01, 1e4);
            #endif
        }

        void main() {
            vec4 c = color;
            c.rgb *= c.a;
            float w = weight(c.a);
            accumColor = vec4(c.rgb * w, c.a);
            accumAlpha = c.a * w;
        }
    </script>
    <script type="x-shader/vs" id="vertex-quad">
        #version 300 es

        layout(location=0) in vec4 aPosition;
        
        void main() {
            gl_Position = aPosition;
        }
    </script>

    <script type="x-shader/fs" id="fragment-draw">
        #version 300 es
        precision highp float;

        uniform sampler2D uAccumulate;
        uniform sampler2D uAccumulateAlpha;
        out vec4 fragColor;
        void main() {
            ivec2 fragCoord = ivec2(gl_FragCoord.xy);
            vec4 accum = texelFetch(uAccumulate, fragCoord, 0);
            float a = 1.0 - accum.a;
            accum.a = texelFetch(uAccumulateAlpha, fragCoord, 0).r;
            fragColor = vec4(a * accum.rgb / clamp(accum.a, 0.001, 50000.0), a);
        }
    </script>
    <script>
        var canvas = document.getElementById("gl-canvas");
        canvas.width = window.innerWidth;
        canvas.height = window.innerHeight;

        var gl = canvas.getContext("webgl2");

        if (!gl) {
            console.error("WebGL 2 not available");
            document.body.innerHTML = "This example requires WebGL 2 which is unavailable on this system."
        }

        if (!gl.getExtension("EXT_color_buffer_float")) {
            console.error("FLOAT color buffer not available");
            document.body.innerHTML = "This example requires EXT_color_buffer_float which is unavailable on this system."
        }

        /////////////////////////
        // OBJECT DESCRIPTIONS
        /////////////////////////

        var CAMERA_Z = 2;
        var NEAR = 2.92;
        var FAR = 3;
        var Z_DELTA = 0.01;

        var QUAD_COLORS = [
            [1, 0, 0],
            [0, 1, 0],
            [0, 0, 1],
            [1, 1, 0],
            [0, 1, 1],
            [1, 0, 1],
            [1, 1, 1]
        ];
        QUAD_ALPHA = 0.95;

        var NUM_QUADS = QUAD_COLORS.length;
        var quads = new Array(NUM_QUADS);

        var x = -1;
        var y = -1;
        var z = -1;

        for (var i = 0; i < NUM_QUADS; ++i) {
            var quadUniformData = new Float32Array(8);
            quadUniformData.set(QUAD_COLORS[i]);
            quadUniformData[3] = QUAD_ALPHA;
            quadUniformData[4] = x;
            quadUniformData[5] = y;
            quadUniformData[6] = z;

            var quadUniformBuffer = gl.createBuffer();
            gl.bindBufferBase(gl.UNIFORM_BUFFER, 1, quadUniformBuffer);
            gl.bufferData(gl.UNIFORM_BUFFER, quadUniformData, gl.STATIC_DRAW);

            quads[i] = {
                uniformBuffer: quadUniformBuffer,
                uniformData: quadUniformData
            };

            x += 0.2;
            y += 0.2;
            z += Z_DELTA;
        }

        /////////////////////////
        // ACCUMULATION PROGRAM
        /////////////////////////

        var accumVsSource =  document.getElementById("vertex-accum").text.trim();
        var accumFsSource =  document.getElementById("fragment-accum").text.trim();
        
        var accumVertexShader = gl.createShader(gl.VERTEX_SHADER);
        gl.shaderSource(accumVertexShader, accumVsSource);
        gl.compileShader(accumVertexShader);

        if (!gl.getShaderParameter(accumVertexShader, gl.COMPILE_STATUS)) {
            console.error(gl.getShaderInfoLog(accumVertexShader));
        }

        var accumFragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
        gl.shaderSource(accumFragmentShader, accumFsSource);
        gl.compileShader(accumFragmentShader);

        if (!gl.getShaderParameter(accumFragmentShader, gl.COMPILE_STATUS)) {
            console.error(gl.getShaderInfoLog(accumFragmentShader));
        }

        var accumProgram = gl.createProgram();
        gl.attachShader(accumProgram, accumVertexShader);
        gl.attachShader(accumProgram, accumFragmentShader);
        gl.linkProgram(accumProgram);

        if (!gl.getProgramParameter(accumProgram, gl.LINK_STATUS)) {
            console.error(gl.getProgramInfoLog(accumProgram));
        }

        /////////////////////
        // DRAW PROGRAM
        /////////////////////

        var quadVsSource =  document.getElementById("vertex-quad").text.trim();
        var drawFsSource = document.getElementById("fragment-draw").text.trim();

        var drawVertexShader = gl.createShader(gl.VERTEX_SHADER);
        gl.shaderSource(drawVertexShader, quadVsSource);
        gl.compileShader(drawVertexShader);

        if (!gl.getShaderParameter(drawVertexShader, gl.COMPILE_STATUS)) {
            console.error(gl.getShaderInfoLog(drawVertexShader));
        }

        var drawFragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
        gl.shaderSource(drawFragmentShader, drawFsSource);
        gl.compileShader(drawFragmentShader);

        if (!gl.getShaderParameter(drawFragmentShader, gl.COMPILE_STATUS)) {
            console.error(gl.getShaderInfoLog(drawFragmentShader));
        }

        var drawProgram = gl.createProgram();
        gl.attachShader(drawProgram, drawVertexShader);
        gl.attachShader(drawProgram, drawFragmentShader);
        gl.linkProgram(drawProgram);

        if (!gl.getProgramParameter(drawProgram, gl.LINK_STATUS)) {
            console.error(gl.getProgramInfoLog(drawProgram));
        }

        /////////////////////////
        // GET UNIFORM LOCATIONS
        /////////////////////////

        var sceneUniformsLocation = gl.getUniformBlockIndex(accumProgram, "SceneUniforms");
        gl.uniformBlockBinding(accumProgram, sceneUniformsLocation, 0);

        var objectUniformsLocation = gl.getUniformBlockIndex(accumProgram, "ObjectUniforms");
        gl.uniformBlockBinding(accumProgram, objectUniformsLocation, 1);

        var accumLocation = gl.getUniformLocation(drawProgram, "uAccumulate");
        var accumAlphaLocation = gl.getUniformLocation(drawProgram, "uAccumulateAlpha");

        ////////////////////////////////
        //  SET UP FRAMEBUFFERS
        ////////////////////////////////

        var accumBuffer = gl.createFramebuffer();

        gl.bindFramebuffer(gl.FRAMEBUFFER, accumBuffer);

        gl.activeTexture(gl.TEXTURE0);
        var accumTarget = gl.createTexture();
        gl.bindTexture(gl.TEXTURE_2D, accumTarget);
        gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
        gl.texStorage2D(gl.TEXTURE_2D, 1, gl.RGBA32F, gl.drawingBufferWidth, gl.drawingBufferHeight);
        gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, accumTarget, 0);

        gl.activeTexture(gl.TEXTURE1);
        var accumAlphaTarget = gl.createTexture();
        gl.bindTexture(gl.TEXTURE_2D, accumAlphaTarget);
        gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
        gl.texStorage2D(gl.TEXTURE_2D, 1, gl.R32F, gl.drawingBufferWidth, gl.drawingBufferHeight);
        gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT1, gl.TEXTURE_2D, accumAlphaTarget, 0);

        gl.activeTexture(gl.TEXTURE2);
        var depthTarget = gl.createTexture();
        gl.bindTexture(gl.TEXTURE_2D, depthTarget);
        gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
        gl.texStorage2D(gl.TEXTURE_2D, 1, gl.DEPTH_COMPONENT32F, gl.drawingBufferWidth, gl.drawingBufferHeight);
        gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.TEXTURE_2D, depthTarget, 0);

        gl.drawBuffers([
            gl.COLOR_ATTACHMENT0,
            gl.COLOR_ATTACHMENT1
        ]);

        gl.bindFramebuffer(gl.FRAMEBUFFER, null);


        gl.useProgram(drawProgram);
        gl.uniform1i(accumLocation, 0);
        gl.uniform1i(accumAlphaLocation, 1);

        /////////////////////
        // SET UP GEOMETRY
        /////////////////////

        // Quad for draw pass
        var quadArray = gl.createVertexArray();
        gl.bindVertexArray(quadArray);

        var quadPositionBuffer = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, quadPositionBuffer);
        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
            -1, 1,
            -1, -1,
            1, -1,
            -1, 1,
            1, -1,
            1, 1,
        ]), gl.STATIC_DRAW);
        gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
        gl.enableVertexAttribArray(0);

        //////////////////////
        // SET UP UNIFORMS
        //////////////////////

        var projMatrix = new Float32Array(16);
        mat4Perspective(projMatrix, Math.PI / 2, canvas.width / canvas.height, NEAR, FAR);

        var viewMatrix = new Float32Array(16);
        mat4LookAt(viewMatrix, [0, 0, CAMERA_Z], [0, 0, 0], [0, 1, 0]);

        var viewProjMatrix = new Float32Array(16);
        mat4Multiply(viewProjMatrix, projMatrix, viewMatrix);

        var sceneUniformData = new Float32Array(16);
        sceneUniformData.set(viewProjMatrix);

        var sceneUniformBuffer = gl.createBuffer();
        gl.bindBufferBase(gl.UNIFORM_BUFFER, 0, sceneUniformBuffer);
        gl.bufferData(gl.UNIFORM_BUFFER, sceneUniformData, gl.STATIC_DRAW);

        gl.bindVertexArray(quadArray);

        
        gl.enable(gl.BLEND);
        gl.depthMask(false);

        function draw() {

            ////////////////////
            // DRAW BOXES
            ////////////////////
            gl.bindFramebuffer(gl.FRAMEBUFFER, accumBuffer);
            gl.useProgram(accumProgram);
            gl.clearColor(0.0, 0.0, 0.0, 1.0);
            gl.clear(gl.COLOR_BUFFER_BIT);
            gl.blendFuncSeparate(gl.ONE, gl.ONE, gl.ZERO, gl.ONE_MINUS_SRC_ALPHA);

            for (var i = 0, len = quads.length; i < len; ++i) {
                gl.bindBufferBase(gl.UNIFORM_BUFFER, 1, quads[i].uniformBuffer);
                gl.drawArrays(gl.TRIANGLES, 0, 6);
            }


            /////////
            // DRAW
            /////////
            gl.bindFramebuffer(gl.FRAMEBUFFER, null);
            gl.useProgram(drawProgram);
            gl.clearColor(0.75, 0.75, 0.75, 1.0);
            gl.clear(gl.COLOR_BUFFER_BIT);
            gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
            gl.drawArrays(gl.TRIANGLES, 0, 6);                

            // requestAnimationFrame(draw);
        }

        requestAnimationFrame(draw);

        // Utility mat functions

        function mat4Perspective(out, fovy, aspect, near, far) {
            var f = 1.0 / Math.tan(fovy / 2),
                nf = 1 / (near - far);
            out[0] = f / aspect;
            out[1] = 0;
            out[2] = 0;
            out[3] = 0;
            out[4] = 0;
            out[5] = f;
            out[6] = 0;
            out[7] = 0;
            out[8] = 0;
            out[9] = 0;
            out[10] = (far + near) * nf;
            out[11] = -1;
            out[12] = 0;
            out[13] = 0;
            out[14] = (2 * far * near) * nf;
            out[15] = 0;
            return out;
        };

        function mat4Multiply(out, a, b) {
            var a00 = a[0], a01 = a[1], a02 = a[2], a03 = a[3],
                a10 = a[4], a11 = a[5], a12 = a[6], a13 = a[7],
                a20 = a[8], a21 = a[9], a22 = a[10], a23 = a[11],
                a30 = a[12], a31 = a[13], a32 = a[14], a33 = a[15];

            // Cache only the current line of the second matrix
            var b0  = b[0], b1 = b[1], b2 = b[2], b3 = b[3];
            out[0] = b0*a00 + b1*a10 + b2*a20 + b3*a30;
            out[1] = b0*a01 + b1*a11 + b2*a21 + b3*a31;
            out[2] = b0*a02 + b1*a12 + b2*a22 + b3*a32;
            out[3] = b0*a03 + b1*a13 + b2*a23 + b3*a33;

            b0 = b[4]; b1 = b[5]; b2 = b[6]; b3 = b[7];
            out[4] = b0*a00 + b1*a10 + b2*a20 + b3*a30;
            out[5] = b0*a01 + b1*a11 + b2*a21 + b3*a31;
            out[6] = b0*a02 + b1*a12 + b2*a22 + b3*a32;
            out[7] = b0*a03 + b1*a13 + b2*a23 + b3*a33;

            b0 = b[8]; b1 = b[9]; b2 = b[10]; b3 = b[11];
            out[8] = b0*a00 + b1*a10 + b2*a20 + b3*a30;
            out[9] = b0*a01 + b1*a11 + b2*a21 + b3*a31;
            out[10] = b0*a02 + b1*a12 + b2*a22 + b3*a32;
            out[11] = b0*a03 + b1*a13 + b2*a23 + b3*a33;

            b0 = b[12]; b1 = b[13]; b2 = b[14]; b3 = b[15];
            out[12] = b0*a00 + b1*a10 + b2*a20 + b3*a30;
            out[13] = b0*a01 + b1*a11 + b2*a21 + b3*a31;
            out[14] = b0*a02 + b1*a12 + b2*a22 + b3*a32;
            out[15] = b0*a03 + b1*a13 + b2*a23 + b3*a33;
            return out;
        };

        function mat4LookAt(out, eye, center, up) {
            var x0, x1, x2, y0, y1, y2, z0, z1, z2, len,
                eyex = eye[0],
                eyey = eye[1],
                eyez = eye[2],
                upx = up[0],
                upy = up[1],
                upz = up[2],
                centerx = center[0],
                centery = center[1],
                centerz = center[2];

            z0 = eyex - centerx;
            z1 = eyey - centery;
            z2 = eyez - centerz;

            len = 1 / Math.sqrt(z0 * z0 + z1 * z1 + z2 * z2);
            z0 *= len;
            z1 *= len;
            z2 *= len;

            x0 = upy * z2 - upz * z1;
            x1 = upz * z0 - upx * z2;
            x2 = upx * z1 - upy * z0;
            len = Math.sqrt(x0 * x0 + x1 * x1 + x2 * x2);
            if (!len) {
                x0 = 0;
                x1 = 0;
                x2 = 0;
            } else {
                len = 1 / len;
                x0 *= len;
                x1 *= len;
                x2 *= len;
            }

            y0 = z1 * x2 - z2 * x1;
            y1 = z2 * x0 - z0 * x2;
            y2 = z0 * x1 - z1 * x0;

            len = Math.sqrt(y0 * y0 + y1 * y1 + y2 * y2);
            if (!len) {
                y0 = 0;
                y1 = 0;
                y2 = 0;
            } else {
                len = 1 / len;
                y0 *= len;
                y1 *= len;
                y2 *= len;
            }

            out[0] = x0;
            out[1] = y0;
            out[2] = z0;
            out[3] = 0;
            out[4] = x1;
            out[5] = y1;
            out[6] = z1;
            out[7] = 0;
            out[8] = x2;
            out[9] = y2;
            out[10] = z2;
            out[11] = 0;
            out[12] = -(x0 * eyex + x1 * eyey + x2 * eyez);
            out[13] = -(y0 * eyex + y1 * eyey + y2 * eyez);
            out[14] = -(z0 * eyex + z1 * eyey + z2 * eyez);
            out[15] = 1;

            return out;
        };

    </script>
    <a href="https://github.com/tsherif/webgl2examples" id="github-ribbon"><img style="position: absolute; top: 0; right: 0; border: 0;" src="https://camo.githubusercontent.com/365986a132ccd6a44c23a9169022c0b5c890c387/68747470733a2f2f73332e616d617a6f6e6177732e636f6d2f6769746875622f726962626f6e732f666f726b6d655f72696768745f7265645f6161303030302e706e67" alt="Fork me on GitHub" data-canonical-src="https://s3.amazonaws.com/github/ribbons/forkme_right_red_aa0000.png"></a>
</body>
</html>
